/****************************************************** TODO: Organize skills? More skillz?  ************************************************************/
//							Exp stealing? More points for hurt higher level? License for attack skills? Jump, Run, Immunity, box Defense skills

include("exp_settings.lua")
include("exp_powerups.lua")
AddCSLuaFile("autorun/client/exp_hud.lua")

// Let's make the files so we can store the exp
sql.Query("CREATE TABLE IF NOT EXISTS exp_pdata('steam' TEXT NOT NULL, 'exp' INTEGER NOT NULL, 'curlevel' INTEGER NOT NULL, 'nextlevel' INTEGER NOT NULL, 'levelnum' INTEGER NOT NULL, PRIMARY KEY('steam'));")
if not file.Exists("exp_store.txt") and EXP_USETXT == 1 then file.Write("exp_store.txt","") end

resource.AddFile("sound/"..EXP_SOUNDPATH) // So everyone gets to hear the fun sound

// Just a quick function to print things into everyone's chat
local function PrintAll(msg) for _,v in ipairs(player.GetAll()) do v:ChatPrint(msg) end end

local meta = FindMetaTable("Player")

CreateConVar("exp_toggle","1",{FCVAR_SERVER_CAN_EXECUTE})

// Some writing/reading functions for text/SQL
local function WriteTextData(ply) 
	local lines = string.Explode("\n",file.Read("exp_store.txt"))
	local towrite = ""
	for k,v in ipairs(lines) do 
		local values = string.Explode(" ",v)
		if values[1] != ply:SteamID() then towrite = towrite..v
		else end
	end
	file.Write("exp_store.txt",
	file.Read("exp_store.txt")..ply:SteamID().." "..ply:GetNWInt("Exp").." "..ply:GetNWInt("CurLevel").." "..ply:GetNWInt("NextLevel").." "..ply:GetNWInt("LevelNum").."\n")
end

local function LoadTextData(ply)
	local lines = string.Explode("\n",file.Read("exp_store.txt"))
	for _,v in ipairs(lines) do
		local values = string.Explode(" ",v)
		if values[1] == ply:SteamID() then
			ply:SetNWInt("Exp",values[2])
			ply:SetNWInt("CurLevel",values[3])
			ply:SetNWInt("NextLevel",values[4])
			ply:SetNWInt("LevelNum",values[5])
		end
	end
end

local function SaveSQLData(ply)
	local exp = ply:GetNWInt("Exp") 
	local cur = ply:GetNWInt("CurLevel")
	local new = ply:GetNWInt("NextLevel")
	local lvl = ply:GetNWInt("LevelNum")
	sql.Begin()
	sql.Query("UPDATE exp_pdata SET exp = "..exp.." WHERE steam = "..sql.SQLStr(ply:SteamID())..";") 
	sql.Query("UPDATE exp_pdata SET curlevel = "..cur.." WHERE STEAM = "..sql.SQLStr(ply:SteamID())..";")
	sql.Query("UPDATE exp_pdata SET nextlevel = "..new.." WHERE STEAM = "..sql.SQLStr(ply:SteamID())..";")
	sql.Query("UPDATE exp_pdata SET levelnum = "..lvl.." WHERE STEAM = "..sql.SQLStr(ply:SteamID())..";")
	sql.Commit()
end

local function LoadSQLData(ply)
	sql.Begin()
	ply:SetNWInt("Exp",tonumber(sql.QueryValue("SELECT exp FROM exp_pdata WHERE steam =  "..sql.SQLStr(ply:SteamID())..";")))
	ply:SetNWInt("CurLevel",tonumber(sql.QueryValue("SELECT curlevel FROM exp_pdata WHERE steam =  "..sql.SQLStr(ply:SteamID())..";")))
	ply:SetNWInt("NextLevel",tonumber(sql.QueryValue("SELECT nextlevel FROM exp_pdata WHERE steam =  "..sql.SQLStr(ply:SteamID())..";")))
	ply:SetNWInt("LevelNum",tonumber(sql.QueryValue("SELECT levelnum FROM exp_pdata WHERE steam =  "..sql.SQLStr(ply:SteamID())..";")))
	sql.Commit()
end



/****************************************************** GAMEMODE HOOKS AND STUFF ****************************************************************/

// Give experience function
function GiveEXP(ply,amt) 
	if EXP_STOPATMAX == 1 and ply:GetNWInt("CurLevel") == levelups[table.Count(levelups)] then return end
	if math.random(1,50-ply:GetNWInt("LevelNum")*4) == 5 then ply:SetNWInt("Exp",ply:GetNWInt("Exp")+amt*EXP_CRIT)
		//ply:ChatPrint("Critical hit!")
	else ply:SetNWInt("Exp",ply:GetNWInt("Exp")+amt) end
	local exp = ply:GetNWInt("Exp")
	if (table.HasValue(levelups,exp) or exp >= ply:GetNWInt("NextLevel")) and amt > 0 then
		local leveln = 1
		for k,v in ipairs(levelups) do if v == ply:GetNWInt("NextLevel") then leveln = k end end
		ply:SetNWInt("NextLevel",levelups[leveln+1])
		ply:SetNWInt("CurLevel",levelups[leveln])
		ply:SetNWInt("LevelNum",leveln)
		hook.Call("LevelGained",nil,ply,leveln)
	end
end



local function GetSkillName(str)
	local ret = ""
	for k,v in ipairs(string.Explode("_",str)) do
		if k != table.Count(string.Explode("_",str)) then ret = ret ..string.upper(string.Left(v,1))..string.Right(v,string.len(v)-1).. " "
		else ret = ret ..string.upper(string.Left(v,1))..string.Right(v,string.len(v)-1) end
	end
	return ret
end

// Play some fancy sounds and tell them their prize
hook.Add("LevelGained","LevelTest",function(ply,leveln)
	if leveln == table.Count(levelups) then PrintAll(ply:Name().." has reached the maximum level!") 
	else PrintAll(ply:GetName().." has now reached level "..Leveln.."!") end
	ply:ChatPrint("You have new skills available!")
	for _,v in ipairs(skills) do if v.Level == leveln then ply:ChatPrint("	- "..GetSkillName(v.Name)) end end
	ply:SendLua("surface.PlaySound(\""..EXP_SOUNDPATH.."\")")
end)


// Give experience/level up on hit an npc or player
function AddEXPOnHit( ply, hitgroup, dmginfo )
	local attacker = dmginfo:GetAttacker()
	if attacker:IsPlayer() then
		GiveEXP(attacker,EXP_INC)
	end
end

function AddEXPOnDeath(vic,wep,killer)
	if killer:IsPlayer() then GiveEXP(killer,EXP_INC*EXP_DEATHMUL-EXP_INC) end
end
		
// Check if we want to use NPCs or Players as the object of exp	
if EXP_USENPC == 1 then 
	hook.Add("ScaleNPCDamage","NPCExp",AddEXPOnHit)
	hook.Add("OnNPCKilled","NPCDeath",AddEXPOnDeath)
elseif EXP_USENPC == 2 then
	hook.Add("ScaleNPCDamage","NPCExp",AddEXPOnHit)
	hook.Add("OnNPCKilled","NPCDeath",AddEXPOnDeath)
	hook.Add("ScalePlayerDamage","PlayerExp",AddEXPOnHit) 
	hook.Add("PlayerDeath","PlayerDeath",AddEXPOnDeath)
else 
	hook.Add("ScalePlayerDamage","PlayerExp",AddEXPOnHit) 
	hook.Add("PlayerDeath","PlayerDeath",AddEXPOnDeath)
end


// Give experience when they throw things with the gravgun
hook.Add("GravGunPunt","GravEXP",function(ply,ent) GiveEXP(ply,EXP_INC) end)


// Take away experience on death
hook.Add("PlayerDeath","DownEXP",function(ply,wep,killer)
	local exp = ply:GetNWInt("Exp")
	local cur = ply:GetNWInt("CurLevel")
	local loss = math.Round(exp-exp*EXP_DEATHLOSS)
	if loss < 0 then ply:SetNWInt("Exp",0)
	elseif loss < cur then
		if killer:GetNWInt("CurLevel") < cur and killer:IsPlayer() then
			ply:SetNWInt("Exp",loss)
			ply:SetNWInt("NewLevel",cur)
			ply:SetNWInt("LevelNum",ply:GetNWInt("LevelNum")-1)
			ply:SetNWInt("CurLevel",levelups[ply:GetNWInt("LevelNum")])
			killer:ChatPrint("You made "..ply:Name().." lose a level!")
		else ply:SetNWInt("Exp",cur) end
	else ply:SetNWInt("Exp",loss) end
end)


// Save the values in the SQL on disconnect
hook.Add("PlayerDisconnected","UpdateEXP",function(ply) if EXP_USETXT == 1 then WriteTextData(ply) else WriteSQLData(ply) end end)


// Load or create the player's values on initial connection
hook.Add("PlayerInitialSpawn","RegisterEXP",function(ply)
	if not tonumber(sql.QueryValue("SELECT exp FROM exp_pdata WHERE steam =  "..sql.SQLStr(ply:SteamID())..";")) then
		sql.Query("INSERT INTO exp_pdata VALUES("..sql.SQLStr(ply:SteamID())..",".. 0 ..",".. 0 ..","..Levelups[2]..",".. 1 ..");")
		ply:SetNWInt("Exp",0)
		ply:SetNWInt("CurLevel",0)
		ply:SetNWInt("NextLevel",levelups[2])
		ply:SetNWInt("LevelNum",1)
	else LoadSQLData(ply) end
end)

function meta:UseSkill(skilln)
	if GetConVarNumber("exp_toggle") == 0 then self:ChatPrint("Skills are not enabled at this time.") return end
	local skused
	if type(skilln) == "string" and not tonumber(skilln) then 
		for _,v in pairs(skills) do 
			if v.Name == string.lower(skilln) or string.lower(GetSkillName(v.Name)) == string.lower(skilln) then skused = v end
		end 
		if not skused then self:ChatPrint("That is not a valid skill name.") return end
	elseif type(tonumber(skilln)) == "number" then
		skilln = tonumber(skilln)
		if table.Count(skills) < skilln then self:ChatPrint("There are only "..table.Count(skills).." possible skills at the moment.") return end
		skused = skills[skilln]
		PrintTable(skused)
	end
	
	self._SkillWait = self._SkillWait or {}
	local wait = self._SkillWait[skused.ID] or 0
	if not self._Allowed[tostring(skused.ID)] then self:ChatPrint("You have not bought this skill yet!") return end
	if self:GetNWBool("exp_silence") then self:ChatPrint("You have been silenced!") return end
	//if self:GetNWInt("LevelNum") < skused.Level then self:ChatPrint("You must be level "..skused.Level.." to use this skill!") return end
	if wait > CurTime() then self:ChatPrint("You must wait ".. math.ceil(wait - CurTime()).. " seconds before using "..GetSkillName(skused.Name).." again.") return end
	
	skused.Func(self)
	self._SkillWait[skused.ID] = CurTime() + skused.Wait
	timer.Simple(skused.Wait,function() self:ChatPrint("Your "..GetSkillName(skused.Name).." skill is now recharged.") end) 
end

function meta:SetTarget(ent)
	if GetConVarNumber("exp_toggle") == 0 then self:ChatPrint("Targets are not enabled at this time.") return end
	if not ent:IsPlayer() and ent:GetClass() != "prop_physics" and not ent:IsNPC() then self:ChatPrint("Not a valid target!") return end
	self._Target = ent
	umsg.Start("exp_target",self)
		umsg.Entity(ent)
	umsg.End()
	//if ent:IsPlayer() then self:ChatPrint("Targeting "..ent:Name()..".")
	//else self:ChatPrint("Targeting " .. ent:GetClass()..".") end
end

function meta:GetTarget() return self._Target or false end

function meta:RemoveTarget()
	self._Target = false
	umsg.Start("exp_target",self)
		umsg.Entity(GetWorldEntity())
	umsg.End()
end

hook.Add("Think","RemoveTargets",function()
	for _,v in ipairs(player.GetAll()) do
		local tar = v:GetTarget()
		if !tar then v:RemoveTarget() return end
		if tar:IsPlayer() then 
			if not tar:Alive() then 
				v:RemoveTarget() 
			end
		elseif tar:IsNPC() then
			if tar:Health() <= 0 then
				v:RemoveTarget()
			end
		elseif !tar:IsValid() or !ValidEntity(tar) then 
			v:RemoveTarget()
		end
	end
end)
	

/********************************************************** CONCOMMANDS **********************************************************************/

local chatcommands = {}


// Allow admins to change the exp level of players
concommand.Add("exp_setlevel",function(ply,cmd,args)
	if ply:IsAdmin() then
		leveln = tonumber(args[2])
		
		local tar
		for _,v in ipairs(player.GetAll()) do if string.match(v:Name(),args[1]) then tar = v end end
		
		if not tar then ply:ChatPrint("Please use a valid target! (format <player name> <level>") return end
		if leveln < 1 or leveln > (table.Count(levelups)+1) then ply:ChatPrint("Please select a valid level between 1 - ".. table.Count(levelups) .. ".") return end
		
		tar:SetNWInt("Exp",levelups[leveln])
		tar:SetNWInt("CurLevel",levelups[leveln])
		if leveln == table.Count(levelups) then tar:SetNWInt("NextLevel",levelups[leveln])
		else tar:SetNWInt("NextLevel",levelups[leveln+1]) end
		ply:SetNWInt("LevelNum",leveln)
		
		ply:ChatPrint("You set "..tar:Name().."'s level to "..Leveln..".")
	else ply:ChatPrint("You must be an admin to use this command.") end
end)
table.insert(chatcommands,{"setlevel","exp_setlevel <player name> <level number>","Set a player's experience level",true})

// Set a target
concommand.Add("exp_target",function(pl,cmd,args)
	pl:SetTarget(pl:GetEyeTrace().Entity)
end)

// Remove a target
concommand.Add("exp_removetarget",function(pl)
	pl:RemoveTarget()
end)

// Allow admins to change the exp increment per hit via console
concommand.Add("exp_setinc",function(ply,cmd,args) 
	if ply:IsAdmin() then 
		local inc = tonumber(args[1])
		if inc < 1 then ply:ChatPrint("Please use a positive integer above zero.") return end
		EXP_INC = tonumber(args[1]) 
	else ply:ChatPrint("You must be an admin to use this command.") end
end)
table.insert(chatcommands,{"setinc","exp_setinc <inc number>","Set how much experience you get per hit",true})


// Allow anyone to check their current level
concommand.Add("exp_curlevel",function(ply,cmd,args)
	for k,v in ipairs(levelups) do if ply:GetNWInt("CurLevel") == v then ply:ChatPrint("You are on level "..k..".") end end
end)
table.insert(chatcommands,{"curlevel","exp_curlevel","Prints your current level",false})


// Allow admins to save everyone's data in the SQL
concommand.Add("exp_savedata",function(ply,cmd,args)
	if ply:IsAdmin() then
		sql.Begin()
		for _,v in pairs(player.GetAll()) do
			if EXP_USETXT == 1 then WriteTextData(v)
			else SaveSQLData(v) end
		end
		sql.Commit()
		PrintAll("Your Exp data was saved!")
	else ply:ChatPrint("You must be an admin to use this command.") end
end)
table.insert(chatcommands,{"savedata","exp_savedata","Saves all players' current exp to the database",true})

concommand.Add("exp_skill",function(pl,cmd,args)
	PrintTable(skills[tonumber(args[1])])
end)

// Allow admins to load data from the previous save
concommand.Add("exp_loaddata",function(ply,cmd,args) 
	if ply:IsAdmin() then
		if ply:GetNWBool("HasLoaded") == false then 
			ply:ChatPrint("WARNING: All players will lose progress made after last save! This loads data from the previous save!")
			ply:ChatPrint("Use this command again if you are sure you want to do this!")
			ply:SetNWBool("HasLoaded",true)
		else
			for _,v in ipairs(player.GetAll()) do LoadSQLData(v) end 
			PrintAll(ply:Name().." has loaded all players' previous save!")
			ply:SetNWBool("HasLoaded",false)
		end
		timer.Simple(10,function() ply:SetNWBool("HasLoaded",false) end)
	else ply:ChatPrint("You must be an admin to use this command.") end
end)
table.insert(chatcommands,{"loaddata","exp_loaddata","Loads all players' current exp from the database",true})


// Just in case the SQL fails, let's check why
concommand.Add("exp_debug",function(ply,cmd,args) if sql.LastError() then ply:ChatPrint(sql.LastError()) else ply:ChatPrint("No SQL errors!") end end)
table.insert(chatcommands,{"debug","exp_debug","Print the last known error in the database",false})


// Get the data from the last save
concommand.Add("exp_printsql",function(ply,cmd,args)
	ply:ChatPrint("Data from previous save...")
	ply:ChatPrint("Experience: "..tonumber(sql.QueryValue("SELECT exp FROM exp_pdata WHERE steam =  "..sql.SQLStr(ply:SteamID())..";")))
	ply:ChatPrint("Base Level Experience: "..tonumber(sql.QueryValue("SELECT curlevel FROM exp_pdata WHERE steam =  "..sql.SQLStr(ply:SteamID())..";")))
	ply:ChatPrint("Next Level: "..tonumber(sql.QueryValue("SELECT nextlevel FROM exp_pdata WHERE steam =  "..sql.SQLStr(ply:SteamID())..";")))
	ply:ChatPrint("Level Number: "..tonumber(sql.QueryValue("SELECT levelnum FROM exp_pdata WHERE steam =  "..sql.SQLStr(ply:SteamID())..";")))
end)
table.insert(chatcommands,{"printsql","exp_printsql","Print the exp data from your last save",false})


// Get the data from right now
concommand.Add("exp_printnw",function(ply,cmd,args)
	ply:ChatPrint("Data at this moment...")
	ply:ChatPrint("Experience: "..ply:GetNWInt("Exp"))
	ply:ChatPrint("Base Level Experience: "..ply:GetNWInt("CurLevel"))
	ply:ChatPrint("Next Level: "..ply:GetNWInt("NextLevel"))
	ply:ChatPrint("Level Number: "..ply:GetNWInt("LevelNum"))
end)
table.insert(chatcommands,{"printnw","exp_printnw","Print all of your current exp data",false})

	
// Get all the possible levels
concommand.Add("exp_printlevels",function(ply,cmd,args) for k,v in ipairs(levelups) do ply:ChatPrint("Level "..k.." - "..v.." experience") end end)
table.insert(chatcommands,{"printlevels","exp_printlevels","Print all possible levels",false})

table.insert(chatcommands,{"menu","exp_menu","Bring up skill menu",false})


// Use the various skills available
concommand.Add("exp_useskill",function(ply,cmd,args) 
	local str = ""
	for k,v in ipairs(args) do if k != table.Count(args) then str = str .. v.. " " else str = str..v end end
	ply:UseSkill(str) 
end)
table.insert(chatcommands,{"useskill","exp_useskill <skill number>","Use a skill",false})


// Let's get a nice view of all the commands available to us
concommand.Add("exp_help",function(ply,cmd,args)
	local function Print(msg) ply:PrintMessage(HUD_PRINTCONSOLE,msg) end
	Print("+++++++++++++++++ ExpPlus Support +++++++++++++++++++++")
	Print("\n-- General Help --")
	Print("  * Chat: To use a chatcommand, precede the command with two asterisks (**). \n    The command is always the concommand name without the \"exp_\" prefix. (i.e. \"**printlevels\")")
	Print("  * Gaining EXP: To get EXP, depending on what your local admin decided, you shoot NPCs or other players.\n    Admins, to change this, edit the \"exp_data.lua\" file.")
	Print("  * Powerups: At every level you get stronger and faster. To know specifically how you're getting better,\n    ask your local admin.")
	Print("  * Skills: Like powerups, new abilities, or skills, become available to you at every level.\n    Look at the skill list for more info.")
	Print("\n-- List of Commands --")
	for _,v in ipairs(chatcommands) do 
		if v[4] == false then Print(v[2].." - "..v[3]) 
		else Print(v[2].." - "..v[3].." - ADMIN ONLY") end
	end
	Print("\n-- List of Skills --")
	table.sort(skills,function(a,b) if a.Level == b.Level then return a.Name < b.Name else return a.Level < b.Level end end)
	for k,v in ipairs(skills) do
		if k < 10 then Print("Level "..v.Level..": "..GetSkillName(v.Name).." - "..v.Desc.." (default "..k..")")
		else Print("Level "..v.Level..": "..GetSkillName(v.Name).." - "..v.Desc) end
	end
end)
table.insert(chatcommands,{"help","exp_help","Show this helpful menu",false})

/******************************************************** CHAT COMMANDS **********************************************************************/


// So the chatcommands will work
hook.Add("PlayerSay","EXP_Chat",function(ply,str,toall)
	if string.Left(str,2) != "**" then return str end
	local cmd = string.Explode(" ",string.sub(str,3,string.len(str)))
	local cmdstr = ""
	for k,v in ipairs(cmd) do if k != 1 then cmdstr = cmdstr .. " ".. v end end
	for _,v in ipairs(chatcommands) do
		if cmd[1] == v[1] then ply:ConCommand("exp_"..v[1].." "..cmdstr) end
	end
	return ""
end)


/********** LOLWUT **********
for _,v in ipairs(player.GetAll()) do
	hook.Add("Think",v:SteamID().."icon",function()
		local icon = ents.Create("targeticon")
		icon:SetPos(v:GetPos())
	end)
end */


